Appearance
JVM垃圾回收是Java平台的核心特性之一,负责自动管理内存,避免开发人员手动释放内存。
随着Java技术的发展,JVM垃圾回收器也经历了多代演进。下面我将介绍主要的垃圾回收器,并重点讲述G1和ZGC。
常见垃圾回收器
Serial收集器
- 单线程收集器,工作时需要暂停所有应用线程(STW)
- 简单高效,适用于单CPU环境和内存小于100MB的场景
- 参数:
-XX:+UseSerialGC
ParNew收集器
- Serial收集器的多线程版本
- 常与CMS一起使用作为年轻代收集器
- 参数:
-XX:+UseParNewGC
Parallel Scavenge/Parallel Old
- 注重吞吐量的多线程收集器
- 可以通过参数控制最大暂停时间或吞吐量
- 参数:
-XX:+UseParallelGC
CMS (Concurrent Mark Sweep)
- 以获取最短回收停顿时间为目标的收集器
- 采用标记-清除算法,并发进行大部分工作
- 缺点:内存碎片、CPU资源敏感、浮动垃圾
- 参数:
-XX:+UseConcMarkSweepGC - JDK9标记为废弃,JDK14正式移除
问题: 为什么CMS垃圾回收器被废弃并移除
G1垃圾回收器(Garbage-First)
基本概念
G1(Garbage-First)是Oracle JDK 7引入的垃圾回收器,设计目标是替代CMS收集器。在JDK 9中被设为默认垃圾收集器。
内存布局
G1将堆划分为大小相等的区域(Region),每个区域大小通常为1MB-32MB。区域有四种类型:
- Eden区域:存放新分配对象
- Survivor区域:存放幸存对象
- Old区域:存放长期存活对象
- Humongous区域:存放大对象(超过区域大小50%的对象)
这种设计带来了显著优势:不再严格划分新生代和老年代的物理空间,可以动态调整各代的大小。
工作流程
G1收集器的工作过程可分为以下几个阶段:
- 初始标记(Initial Marking):STW,标记GC Roots直接关联对象
- 并发标记(Concurrent Marking):与应用线程并发执行,标记整个堆中的存活对象
- 最终标记(Final Marking/Remark):STW,处理并发标记阶段遗留的标记任务
- 筛选回收(Cleanup):STW,对各个区域的回收价值和成本进行排序,根据用户期望的停顿时间制定回收计划,然后回收选定的区域
G1的核心特性
- 预测停顿模型:G1会根据历史数据预测每个区域的回收时间,优先回收价值最高(回收空间最大)的区域,实现用户设定的停顿目标(-XX:MaxGCPauseMillis)
- 增量式垃圾回收:G1不需要一次性回收整个堆空间,而是每次只处理部分区域
- 混合式回收(Mixed GC):G1可以同时回收年轻代和部分老年代,避免了完整的老年代GC
- SATB(Snapshot-At-The-Beginning)算法:解决并发标记的问题,降低漏标可能性
G1的调优参数
-XX:+UseG1GC:启用G1-XX:MaxGCPauseMillis=200:设置期望的最大停顿时间(默认200ms)-XX:G1HeapRegionSize=n:设置Region大小,必须是2的幂,范围1MB-32MB-XX:InitiatingHeapOccupancyPercent=45:设置触发标记周期的堆占用阈值(默认45%)
G1的优势
- 可预测的停顿时间,适合大内存、多核CPU环境
- 空间整合能力强,降低内存碎片产生
- 并发能力强,大部分工作与应用线程并发执行
- 适合堆内存较大(6GB+)的系统
ZGC垃圾回收器(Z Garbage Collector)
基本概念
ZGC是JDK 11引入的低延迟垃圾回收器,设计目标是将STW时间控制在10ms以内,即使堆内存达到TB级别。在JDK 15中已成为生产可用状态。
内存布局
ZGC将堆内存划分为大小不等的区域(Z Page),根据大小分为:
- Small (2MB)
- Medium (32MB)
- Large (大于等于32MB)
核心技术
着色指针(Colored Pointers):
ZGC在64位指针中保留了一些位作为标记位,用于标记对象状态:
- Marked0/Marked1位:用于标记活跃对象
- Remapped位:指示对象是否已经被重定位
- Finalizable位:标记是否可终结
读屏障(Load Barrier):
在应用程序访问对象引用时,ZGC会检查指针的标记位,必要时进行处理,确保应用始终能看到正确的引用
并发处理: 几乎所有GC操作都在并发阶段完成,包括并发标记、并发重定位、并发引用处理等
ZGC工作流程
- 并发标记(Concurrent Mark):标记所有可达对象
- 并发准备重定位(Concurrent Prepare for Relocate):选择要清理的区域
- 并发重定位(Concurrent Relocate):将存活对象复制到新位置
- 并发重映射(Concurrent Remap):更新引用,指向对象的新位置
整个过程中STW阶段极短,主要包括初始标记、最终标记等少量操作,大部分工作与应用线程并发执行。
ZGC的调优参数
-XX:+UseZGC:启用ZGC-XX:ZAllocationSpikeTolerance:调整ZGC对内存分配速率突增的容忍度-XX:ConcGCThreads:并发GC线程数-XX:ZCollectionInterval:两次GC之间的最小时间间隔-XX:ZFragmentationLimit:内存碎片百分比阈值
ZGC的优势
- 超低延迟:GC停顿时间不超过10ms,且与堆大小无关
- 可扩展性强:支持8MB-16TB的堆内存范围
- 高吞吐量:即使在TB级别堆上也能保持高吞吐量,只比吞吐量收集器低约15%
- 适合延迟敏感型应用:如金融交易、游戏服务器、实时数据处理系统
G1与ZGC的对比
| 特性 | G1 | ZGC |
|---|---|---|
| 首次发布 | JDK 7 | JDK 11 |
| 默认GC | JDK 9+ | 否 |
| 停顿时间 | 预测停顿,通常在100-200ms | <10ms,与堆大小无关 |
| 内存占用 | 相对较小 | 较高(约10-15%) |
| CPU开销 | 中等 | 较高 |
| 碎片处理 | 较好 | 极佳 |
| 适用场景 | 6GB-100GB的堆内存,可接受短暂停顿 | 超大堆内存,极低延迟要求 |
| JVM参数 | -XX:+UseG1GC | -XX:+UseZGC |
选择建议
- 如果系统内存<4GB,可以考虑使用ParallelGC
- 如果系统内存在4GB-8GB,且关注吞吐量,可以使用G1
- 如果系统对延迟非常敏感,且内存>8GB,建议使用ZGC
- 企业级应用通常G1是较为均衡的选择,能满足大多数场景需求
- 金融、游戏等对延迟极为敏感的领域,ZGC是更好的选择
未来发展
随着JDK的更新,ZGC正在不断完善。JDK 16中已经支持了ZGC的类卸载功能,JDK 17中又进一步优化了ZGC的性能。
未来,ZGC很可能会成为Java默认的垃圾收集器,特别是随着大内存系统的普及和低延迟需求的增加。